﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace Nes4Net
{
    public class Cpu
    {
        public Nes Nes { get; private set; }
        public Int32[] Mem { get; private set; }
        public int REG_ACC { get; private set; }
        public int REG_X { get; private set; }
        public int REG_Y { get; private set; }
        public int REG_SP { get; private set; }
        public int REG_PC { get; private set; }
        public int REG_PC_NEW { get; private set; }
        public int REG_STATUS { get; private set; }
        public int F_CARRY { get; private set; }
        public int F_DECIMAL { get; private set; }
        public int F_INTERRUPT { get; private set; }
        public int F_INTERRUPT_NEW { get; private set; }
        public int F_OVERFLOW { get; private set; }
        public int F_SIGN { get; private set; }
        public int F_ZERO { get; private set; }
        public int F_NOTUSED { get; private set; }
        public int F_NOTUSED_NEW { get; private set; }
        public int F_BRK { get; private set; }
        public int F_BRK_NEW { get; private set; }
        public Int32[] Opdata { get; private set; }
        public bool IrqRequested { get; private set; }
        public Int32 IrqType { get; private set; }

        public Int32 CyclesToHalt;

        public bool Crash { get; private set; }

        public Int32 IRQ_NORMAL = 0;
        public Int32 IRQ_NMI = 1;
        public Int32 IRQ_RESET = 2;

        public Cpu(Nes nes)
        {
            this.Nes = nes;

            // Keep Chrome happy
            //this.Mem = null;
            //this.REG_ACC = null;
            //this.REG_X = null;
            //this.REG_Y = null;
            //this.REG_SP = null;
            //this.REG_PC = null;
            //this.REG_PC_NEW = null;
            //this.REG_STATUS = null;
            //this.F_CARRY = null;
            //this.F_DECIMAL = null;
            //this.F_INTERRUPT = null;
            //this.F_INTERRUPT_NEW = null;
            //this.F_OVERFLOW = null;
            //this.F_SIGN = null;
            //this.F_ZERO = null;
            //this.F_NOTUSED = null;
            //this.F_NOTUSED_NEW = null;
            //this.F_BRK = null;
            //this.F_BRK_NEW = null;
            //this.Opdata = null;
            //this.CyclesToHalt = null;
            //this.Crash = null;
            //this.IrqRequested = null;
            //this.IrqType = null;
            this.Reset();
        }

        public void Reset()
        {
            // Main memory
            this.Mem = new Int32[0x10000];

            for (var i = 0; i < 0x2000; i++)
            {
                this.Mem[i] = 0xff;
            }
            for (var p = 0; p < 4; p++)
            {
                var j = p * 0x800;
                this.Mem[j + 0x008] = 0xf7;
                this.Mem[j + 0x009] = 0xef;
                this.Mem[j + 0x00a] = 0xdf;
                this.Mem[j + 0x00f] = 0xbf;
            }
            for (var k = 0x2001; k < this.Mem.Length; k++)
            {
                this.Mem[k] = 0;
            }

            // CPU Registers:
            this.REG_ACC = 0;
            this.REG_X = 0;
            this.REG_Y = 0;
            // Reset Stack pointer:
            this.REG_SP = 0x01ff;
            // Reset Program counter:
            this.REG_PC = 0x8000 - 1;
            this.REG_PC_NEW = 0x8000 - 1;
            // Reset Status register:
            this.REG_STATUS = 0x28;

            this.SetStatus(0x28);

            // Set flags:
            this.F_CARRY = 0;
            this.F_DECIMAL = 0;
            this.F_INTERRUPT = 1;
            this.F_INTERRUPT_NEW = 1;
            this.F_OVERFLOW = 0;
            this.F_SIGN = 0;
            this.F_ZERO = 1;

            this.F_NOTUSED = 1;
            this.F_NOTUSED_NEW = 1;
            this.F_BRK = 1;
            this.F_BRK_NEW = 1;

            this.Opdata = new OpData().Opdata;
            this.CyclesToHalt = 0;

            // Reset crash flag:
            this.Crash = false;

            // Interrupt notification:
            this.IrqRequested = false;
            // this.IrqType = null;
        }

        public Int32 Emulate()
        {
            Int32 temp;
            Int32 add;

            if (this.IrqRequested)
            {
                temp =
                  this.F_CARRY |
                  ((this.F_ZERO == 0 ? 1 : 0) << 1) |
                  (this.F_INTERRUPT << 2) |
                  (this.F_DECIMAL << 3) |
                  (this.F_BRK << 4) |
                  (this.F_NOTUSED << 5) |
                  (this.F_OVERFLOW << 6) |
                  (this.F_SIGN << 7);

                this.REG_PC_NEW = this.REG_PC;
                this.F_INTERRUPT_NEW = this.F_INTERRUPT;
                switch (this.IrqType)
                {
                    case 0:
                        {
                            // Normal IRQ:
                            if (this.F_INTERRUPT != 0)
                            {
                                // console.log("Interrupt was masked.");
                                break;
                            }
                            this.DoIrq(temp);
                            // console.log("Did normal IRQ. I="+this.F_INTERRUPT);
                            break;
                        }
                    case 1:
                        {
                            // NMI:
                            this.DoNonMaskableInterrupt(temp);
                            break;
                        }
                    case 2:
                        {
                            // Reset:
                            this.DoResetInterrupt();
                            break;
                        }
                }

                this.REG_PC = this.REG_PC_NEW;
                this.F_INTERRUPT = this.F_INTERRUPT_NEW;
                this.F_BRK = this.F_BRK_NEW;
                this.IrqRequested = false;
            }
            var opinf = this.Opdata[this.Nes.Mmap.Load(this.REG_PC + 1)];
            var cycleCount = opinf >> 24;
            var cycleAdd = 0;

            // Find address mode:
            var addrMode = (opinf >> 8) & 0xff;

            // Increment PC by number of op bytes:
            var opaddr = this.REG_PC;
            this.REG_PC += (opinf >> 16) & 0xff;

            var addr = 0;
            switch (addrMode)
            {
                case 0:
                    {
                        // Zero Page mode. Use the address given after the opcode,
                        // but without high byte.
                        addr = this.Load(opaddr + 2);
                        break;
                    }
                case 1:
                    {
                        // Relative mode.
                        addr = this.Load(opaddr + 2);
                        if (addr < 0x80)
                        {
                            addr += this.REG_PC;
                        }
                        else
                        {
                            addr += this.REG_PC - 256;
                        }
                        break;
                    }
                case 2:
                    {
                        // Ignore. Address is implied in instruction.
                        break;
                    }
                case 3:
                    {
                        // Absolute mode. Use the two bytes following the opcode as
                        // an address.
                        addr = this.Load16bit(opaddr + 2);
                        break;
                    }
                case 4:
                    {
                        // Accumulator mode. The address is in the accumulator
                        // register.
                        addr = this.REG_ACC;
                        break;
                    }
                case 5:
                    {
                        // Immediate mode. The value is given after the opcode.
                        addr = this.REG_PC;
                        break;
                    }
                case 6:
                    {
                        // Zero Page Indexed mode, X as index. Use the address given
                        // after the opcode, then add the
                        // X register to it to get the final address.
                        addr = (this.Load(opaddr + 2) + this.REG_X) & 0xff;
                        break;
                    }
                case 7:
                    {
                        // Zero Page Indexed mode, Y as index. Use the address given
                        // after the opcode, then add the
                        // Y register to it to get the final address.
                        addr = (this.Load(opaddr + 2) + this.REG_Y) & 0xff;
                        break;
                    }
                case 8:
                    {
                        // Absolute Indexed Mode, X as index. Same as zero page
                        // indexed, but with the high byte.
                        addr = this.Load16bit(opaddr + 2);
                        if ((addr & 0xff00) != ((addr + this.REG_X) & 0xff00))
                        {
                            cycleAdd = 1;
                        }
                        addr += this.REG_X;
                        break;
                    }
                case 9:
                    {
                        // Absolute Indexed Mode, Y as index. Same as zero page
                        // indexed, but with the high byte.
                        addr = this.Load16bit(opaddr + 2);
                        if ((addr & 0xff00) != ((addr + this.REG_Y) & 0xff00))
                        {
                            cycleAdd = 1;
                        }
                        addr += this.REG_Y;
                        break;
                    }
                case 10:
                    {
                        // Pre-indexed Indirect mode. Find the 16-bit address
                        // starting at the given location plus
                        // the current X register. The value is the contents of that
                        // address.
                        addr = this.Load(opaddr + 2);
                        if ((addr & 0xff00) != ((addr + this.REG_X) & 0xff00))
                        {
                            cycleAdd = 1;
                        }
                        addr += this.REG_X;
                        addr &= 0xff;
                        addr = this.Load16bit(addr);
                        break;
                    }
                case 11:
                    {
                        // Post-indexed Indirect mode. Find the 16-bit address
                        // contained in the given location
                        // (and the one following). Add to that address the contents
                        // of the Y register. Fetch the value
                        // stored at that adress.
                        addr = this.Load16bit(this.Load(opaddr + 2));
                        if ((addr & 0xff00) != ((addr + this.REG_Y) & 0xff00))
                        {
                            cycleAdd = 1;
                        }
                        addr += this.REG_Y;
                        break;
                    }
                case 12:
                    {
                        // Indirect Absolute mode. Find the 16-bit address contained
                        // at the given location.
                        addr = this.Load16bit(opaddr + 2); // Find op
                        if (addr < 0x1fff)
                        {
                            addr =
                              this.Mem[addr] +
                              (this.Mem[(addr & 0xff00) | (((addr & 0xff) + 1) & 0xff)] << 8); // Read from address given in op
                        }
                        else
                        {
                            addr =
                              this.Nes.Mmap.Load(addr) +
                              (this.Nes.Mmap.Load(
                                (addr & 0xff00) | (((addr & 0xff) + 1) & 0xff)
                              ) <<
                                8);
                        }
                        break;
                    }
            }


            // Wrap around for addresses above 0xFFFF:
            addr &= 0xffff;

            // ----------------------------------------------------------------------------------------------------
            // Decode & execute instruction:
            // ----------------------------------------------------------------------------------------------------

            // This should be compiled to a jump table.
            switch (opinf & 0xff)
            {
                case 0:
                    {
                        // *******
                        // * ADC *
                        // *******

                        // Add with carry.
                        temp = this.REG_ACC + this.Load(addr) + this.F_CARRY;

                        if (
                          ((this.REG_ACC ^ this.Load(addr)) & 0x80) == 0 &&
                          ((this.REG_ACC ^ temp) & 0x80) != 0
                        )
                        {
                            this.F_OVERFLOW = 1;
                        }
                        else
                        {
                            this.F_OVERFLOW = 0;
                        }
                        this.F_CARRY = temp > 255 ? 1 : 0;
                        this.F_SIGN = (temp >> 7) & 1;
                        this.F_ZERO = temp & 0xff;
                        this.REG_ACC = temp & 255;
                        cycleCount += cycleAdd;
                        break;
                    }
                case 1:
                    {
                        // *******
                        // * AND *
                        // *******

                        // AND memory with accumulator.
                        this.REG_ACC = this.REG_ACC & this.Load(addr);
                        this.F_SIGN = (this.REG_ACC >> 7) & 1;
                        this.F_ZERO = this.REG_ACC;
                        if (addrMode != 11) cycleCount += cycleAdd; // PostIdxInd = 11
                        break;
                    }
                case 2:
                    {
                        // *******
                        // * ASL *
                        // *******

                        // Shift left one bit
                        if (addrMode == 4)
                        {
                            // ADDR_ACC = 4

                            this.F_CARRY = (this.REG_ACC >> 7) & 1;
                            this.REG_ACC = (this.REG_ACC << 1) & 255;
                            this.F_SIGN = (this.REG_ACC >> 7) & 1;
                            this.F_ZERO = this.REG_ACC;
                        }
                        else
                        {
                            temp = this.Load(addr);
                            this.F_CARRY = (temp >> 7) & 1;
                            temp = (temp << 1) & 255;
                            this.F_SIGN = (temp >> 7) & 1;
                            this.F_ZERO = temp;
                            this.Write(addr, temp);
                        }
                        break;
                    }
                case 3:
                    {
                        // *******
                        // * BCC *
                        // *******

                        // Branch on carry clear
                        if (this.F_CARRY == 0)
                        {
                            cycleCount += (opaddr & 0xff00) != (addr & 0xff00) ? 2 : 1;
                            this.REG_PC = addr;
                        }
                        break;
                    }
                case 4:
                    {
                        // *******
                        // * BCS *
                        // *******

                        // Branch on carry set
                        if (this.F_CARRY == 1)
                        {
                            cycleCount += (opaddr & 0xff00) != (addr & 0xff00) ? 2 : 1;
                            this.REG_PC = addr;
                        }
                        break;
                    }
                case 5:
                    {
                        // *******
                        // * BEQ *
                        // *******

                        // Branch on zero
                        if (this.F_ZERO == 0)
                        {
                            cycleCount += (opaddr & 0xff00) != (addr & 0xff00) ? 2 : 1;
                            this.REG_PC = addr;
                        }
                        break;
                    }
                case 6:
                    {
                        // *******
                        // * BIT *
                        // *******

                        temp = this.Load(addr);
                        this.F_SIGN = (temp >> 7) & 1;
                        this.F_OVERFLOW = (temp >> 6) & 1;
                        temp &= this.REG_ACC;
                        this.F_ZERO = temp;
                        break;
                    }
                case 7:
                    {
                        // *******
                        // * BMI *
                        // *******

                        // Branch on negative result
                        if (this.F_SIGN == 1)
                        {
                            cycleCount++;
                            this.REG_PC = addr;
                        }
                        break;
                    }
                case 8:
                    {
                        // *******
                        // * BNE *
                        // *******

                        // Branch on not zero
                        if (this.F_ZERO != 0)
                        {
                            cycleCount += (opaddr & 0xff00) != (addr & 0xff00) ? 2 : 1;
                            this.REG_PC = addr;
                        }
                        break;
                    }
                case 9:
                    {
                        // *******
                        // * BPL *
                        // *******

                        // Branch on positive result
                        if (this.F_SIGN == 0)
                        {
                            cycleCount += (opaddr & 0xff00) != (addr & 0xff00) ? 2 : 1;
                            this.REG_PC = addr;
                        }
                        break;
                    }
                case 10:
                    {
                        // *******
                        // * BRK *
                        // *******

                        this.REG_PC += 2;
                        this.Push((this.REG_PC >> 8) & 255);
                        this.Push(this.REG_PC & 255);
                        this.F_BRK = 1;

                        this.Push(
                          this.F_CARRY |
                            ((this.F_ZERO == 0 ? 1 : 0) << 1) |
                            (this.F_INTERRUPT << 2) |
                            (this.F_DECIMAL << 3) |
                            (this.F_BRK << 4) |
                            (this.F_NOTUSED << 5) |
                            (this.F_OVERFLOW << 6) |
                            (this.F_SIGN << 7)
                        );

                        this.F_INTERRUPT = 1;
                        //this.REG_PC = load(0xFFFE) | (load(0xFFFF) << 8);
                        this.REG_PC = this.Load16bit(0xfffe);
                        this.REG_PC--;
                        break;
                    }
                case 11:
                    {
                        // *******
                        // * BVC *
                        // *******

                        // Branch on overflow clear
                        if (this.F_OVERFLOW == 0)
                        {
                            cycleCount += (opaddr & 0xff00) != (addr & 0xff00) ? 2 : 1;
                            this.REG_PC = addr;
                        }
                        break;
                    }
                case 12:
                    {
                        // *******
                        // * BVS *
                        // *******

                        // Branch on overflow set
                        if (this.F_OVERFLOW == 1)
                        {
                            cycleCount += (opaddr & 0xff00) != (addr & 0xff00) ? 2 : 1;
                            this.REG_PC = addr;
                        }
                        break;
                    }
                case 13:
                    {
                        // *******
                        // * CLC *
                        // *******

                        // Clear carry flag
                        this.F_CARRY = 0;
                        break;
                    }
                case 14:
                    {
                        // *******
                        // * CLD *
                        // *******

                        // Clear decimal flag
                        this.F_DECIMAL = 0;
                        break;
                    }
                case 15:
                    {
                        // *******
                        // * CLI *
                        // *******

                        // Clear interrupt flag
                        this.F_INTERRUPT = 0;
                        break;
                    }
                case 16:
                    {
                        // *******
                        // * CLV *
                        // *******

                        // Clear overflow flag
                        this.F_OVERFLOW = 0;
                        break;
                    }
                case 17:
                    {
                        // *******
                        // * CMP *
                        // *******

                        // Compare memory and accumulator:
                        temp = this.REG_ACC - this.Load(addr);
                        this.F_CARRY = temp >= 0 ? 1 : 0;
                        this.F_SIGN = (temp >> 7) & 1;
                        this.F_ZERO = temp & 0xff;
                        cycleCount += cycleAdd;
                        break;
                    }
                case 18:
                    {
                        // *******
                        // * CPX *
                        // *******

                        // Compare memory and index X:
                        temp = this.REG_X - this.Load(addr);
                        this.F_CARRY = temp >= 0 ? 1 : 0;
                        this.F_SIGN = (temp >> 7) & 1;
                        this.F_ZERO = temp & 0xff;
                        break;
                    }
                case 19:
                    {
                        // *******
                        // * CPY *
                        // *******

                        // Compare memory and index Y:
                        temp = this.REG_Y - this.Load(addr);
                        this.F_CARRY = temp >= 0 ? 1 : 0;
                        this.F_SIGN = (temp >> 7) & 1;
                        this.F_ZERO = temp & 0xff;
                        break;
                    }
                case 20:
                    {
                        // *******
                        // * DEC *
                        // *******

                        // Decrement memory by one:
                        temp = (this.Load(addr) - 1) & 0xff;
                        this.F_SIGN = (temp >> 7) & 1;
                        this.F_ZERO = temp;
                        this.Write(addr, temp);
                        break;
                    }
                case 21:
                    {
                        // *******
                        // * DEX *
                        // *******

                        // Decrement index X by one:
                        this.REG_X = (this.REG_X - 1) & 0xff;
                        this.F_SIGN = (this.REG_X >> 7) & 1;
                        this.F_ZERO = this.REG_X;
                        break;
                    }
                case 22:
                    {
                        // *******
                        // * DEY *
                        // *******

                        // Decrement index Y by one:
                        this.REG_Y = (this.REG_Y - 1) & 0xff;
                        this.F_SIGN = (this.REG_Y >> 7) & 1;
                        this.F_ZERO = this.REG_Y;
                        break;
                    }
                case 23:
                    {
                        // *******
                        // * EOR *
                        // *******

                        // XOR Memory with accumulator, store in accumulator:
                        this.REG_ACC = (this.Load(addr) ^ this.REG_ACC) & 0xff;
                        this.F_SIGN = (this.REG_ACC >> 7) & 1;
                        this.F_ZERO = this.REG_ACC;
                        cycleCount += cycleAdd;
                        break;
                    }
                case 24:
                    {
                        // *******
                        // * INC *
                        // *******

                        // Increment memory by one:
                        temp = (this.Load(addr) + 1) & 0xff;
                        this.F_SIGN = (temp >> 7) & 1;
                        this.F_ZERO = temp;
                        this.Write(addr, temp & 0xff);
                        break;
                    }
                case 25:
                    {
                        // *******
                        // * INX *
                        // *******

                        // Increment index X by one:
                        this.REG_X = (this.REG_X + 1) & 0xff;
                        this.F_SIGN = (this.REG_X >> 7) & 1;
                        this.F_ZERO = this.REG_X;
                        break;
                    }
                case 26:
                    {
                        // *******
                        // * INY *
                        // *******

                        // Increment index Y by one:
                        this.REG_Y++;
                        this.REG_Y &= 0xff;
                        this.F_SIGN = (this.REG_Y >> 7) & 1;
                        this.F_ZERO = this.REG_Y;
                        break;
                    }
                case 27:
                    {
                        // *******
                        // * JMP *
                        // *******

                        // Jump to new location:
                        this.REG_PC = addr - 1;
                        break;
                    }
                case 28:
                    {
                        // *******
                        // * JSR *
                        // *******

                        // Jump to new location, saving return address.
                        // Push return address on stack:
                        this.Push((this.REG_PC >> 8) & 255);
                        this.Push(this.REG_PC & 255);
                        this.REG_PC = addr - 1;
                        break;
                    }
                case 29:
                    {
                        // *******
                        // * LDA *
                        // *******

                        // Load accumulator with memory:
                        this.REG_ACC = this.Load(addr);
                        this.F_SIGN = (this.REG_ACC >> 7) & 1;
                        this.F_ZERO = this.REG_ACC;
                        cycleCount += cycleAdd;
                        break;
                    }
                case 30:
                    {
                        // *******
                        // * LDX *
                        // *******

                        // Load index X with memory:
                        this.REG_X = this.Load(addr);
                        this.F_SIGN = (this.REG_X >> 7) & 1;
                        this.F_ZERO = this.REG_X;
                        cycleCount += cycleAdd;
                        break;
                    }
                case 31:
                    {
                        // *******
                        // * LDY *
                        // *******

                        // Load index Y with memory:
                        this.REG_Y = this.Load(addr);
                        this.F_SIGN = (this.REG_Y >> 7) & 1;
                        this.F_ZERO = this.REG_Y;
                        cycleCount += cycleAdd;
                        break;
                    }
                case 32:
                    {
                        // *******
                        // * LSR *
                        // *******

                        // Shift right one bit:
                        if (addrMode == 4)
                        {
                            // ADDR_ACC

                            temp = this.REG_ACC & 0xff;
                            this.F_CARRY = temp & 1;
                            temp >>= 1;
                            this.REG_ACC = temp;
                        }
                        else
                        {
                            temp = this.Load(addr) & 0xff;
                            this.F_CARRY = temp & 1;
                            temp >>= 1;
                            this.Write(addr, temp);
                        }
                        this.F_SIGN = 0;
                        this.F_ZERO = temp;
                        break;
                    }
                case 33:
                    {
                        // *******
                        // * NOP *
                        // *******

                        // No OPeration.
                        // Ignore.
                        break;
                    }
                case 34:
                    {
                        // *******
                        // * ORA *
                        // *******

                        // OR memory with accumulator, store in accumulator.
                        temp = (this.Load(addr) | this.REG_ACC) & 255;
                        this.F_SIGN = (temp >> 7) & 1;
                        this.F_ZERO = temp;
                        this.REG_ACC = temp;
                        if (addrMode != 11) cycleCount += cycleAdd; // PostIdxInd = 11
                        break;
                    }
                case 35:
                    {
                        // *******
                        // * PHA *
                        // *******

                        // Push accumulator on stack
                        this.Push(this.REG_ACC);
                        break;
                    }
                case 36:
                    {
                        // *******
                        // * PHP *
                        // *******

                        // Push processor status on stack
                        this.F_BRK = 1;
                        this.Push(
                          this.F_CARRY |
                            ((this.F_ZERO == 0 ? 1 : 0) << 1) |
                            (this.F_INTERRUPT << 2) |
                            (this.F_DECIMAL << 3) |
                            (this.F_BRK << 4) |
                            (this.F_NOTUSED << 5) |
                            (this.F_OVERFLOW << 6) |
                            (this.F_SIGN << 7)
                        );
                        break;
                    }
                case 37:
                    {
                        // *******
                        // * PLA *
                        // *******

                        // Pull accumulator from stack
                        this.REG_ACC = this.Pull();
                        this.F_SIGN = (this.REG_ACC >> 7) & 1;
                        this.F_ZERO = this.REG_ACC;
                        break;
                    }
                case 38:
                    {
                        // *******
                        // * PLP *
                        // *******

                        // Pull processor status from stack
                        temp = this.Pull();
                        this.F_CARRY = temp & 1;
                        this.F_ZERO = ((temp >> 1) & 1) == 1 ? 0 : 1;
                        this.F_INTERRUPT = (temp >> 2) & 1;
                        this.F_DECIMAL = (temp >> 3) & 1;
                        this.F_BRK = (temp >> 4) & 1;
                        this.F_NOTUSED = (temp >> 5) & 1;
                        this.F_OVERFLOW = (temp >> 6) & 1;
                        this.F_SIGN = (temp >> 7) & 1;

                        this.F_NOTUSED = 1;
                        break;
                    }
                case 39:
                    {
                        // *******
                        // * ROL *
                        // *******

                        // Rotate one bit left
                        if (addrMode == 4)
                        {
                            // ADDR_ACC = 4

                            temp = this.REG_ACC;
                            add = this.F_CARRY;
                            this.F_CARRY = (temp >> 7) & 1;
                            temp = ((temp << 1) & 0xff) + add;
                            this.REG_ACC = temp;
                        }
                        else
                        {
                            temp = this.Load(addr);
                            add = this.F_CARRY;
                            this.F_CARRY = (temp >> 7) & 1;
                            temp = ((temp << 1) & 0xff) + add;
                            this.Write(addr, temp);
                        }
                        this.F_SIGN = (temp >> 7) & 1;
                        this.F_ZERO = temp;
                        break;
                    }
                case 40:
                    {
                        // *******
                        // * ROR *
                        // *******

                        // Rotate one bit right
                        if (addrMode == 4)
                        {
                            // ADDR_ACC = 4

                            add = this.F_CARRY << 7;
                            this.F_CARRY = this.REG_ACC & 1;
                            temp = (this.REG_ACC >> 1) + add;
                            this.REG_ACC = temp;
                        }
                        else
                        {
                            temp = this.Load(addr);
                            add = this.F_CARRY << 7;
                            this.F_CARRY = temp & 1;
                            temp = (temp >> 1) + add;
                            this.Write(addr, temp);
                        }
                        this.F_SIGN = (temp >> 7) & 1;
                        this.F_ZERO = temp;
                        break;
                    }
                case 41:
                    {
                        // *******
                        // * RTI *
                        // *******

                        // Return from interrupt. Pull status and PC from stack.

                        temp = this.Pull();
                        this.F_CARRY = temp & 1;
                        this.F_ZERO = ((temp >> 1) & 1) == 0 ? 1 : 0;
                        this.F_INTERRUPT = (temp >> 2) & 1;
                        this.F_DECIMAL = (temp >> 3) & 1;
                        this.F_BRK = (temp >> 4) & 1;
                        this.F_NOTUSED = (temp >> 5) & 1;
                        this.F_OVERFLOW = (temp >> 6) & 1;
                        this.F_SIGN = (temp >> 7) & 1;

                        this.REG_PC = this.Pull();
                        this.REG_PC += this.Pull() << 8;
                        if (this.REG_PC == 0xffff)
                        {
                            return 0;
                        }
                        this.REG_PC--;
                        this.F_NOTUSED = 1;
                        break;
                    }
                case 42:
                    {
                        // *******
                        // * RTS *
                        // *******

                        // Return from subroutine. Pull PC from stack.

                        this.REG_PC = this.Pull();
                        this.REG_PC += this.Pull() << 8;

                        if (this.REG_PC == 0xffff)
                        {
                            return 0; // return from NSF play routine:
                        }
                        break;
                    }
                case 43:
                    {
                        // *******
                        // * SBC *
                        // *******

                        temp = this.REG_ACC - this.Load(addr) - (1 - this.F_CARRY);
                        this.F_SIGN = (temp >> 7) & 1;
                        this.F_ZERO = temp & 0xff;
                        if (
                          ((this.REG_ACC ^ temp) & 0x80) != 0 &&
                          ((this.REG_ACC ^ this.Load(addr)) & 0x80) != 0
                        )
                        {
                            this.F_OVERFLOW = 1;
                        }
                        else
                        {
                            this.F_OVERFLOW = 0;
                        }
                        this.F_CARRY = temp < 0 ? 0 : 1;
                        this.REG_ACC = temp & 0xff;
                        if (addrMode != 11) cycleCount += cycleAdd; // PostIdxInd = 11
                        break;
                    }
                case 44:
                    {
                        // *******
                        // * SEC *
                        // *******

                        // Set carry flag
                        this.F_CARRY = 1;
                        break;
                    }
                case 45:
                    {
                        // *******
                        // * SED *
                        // *******

                        // Set decimal mode
                        this.F_DECIMAL = 1;
                        break;
                    }
                case 46:
                    {
                        // *******
                        // * SEI *
                        // *******

                        // Set interrupt disable status
                        this.F_INTERRUPT = 1;
                        break;
                    }
                case 47:
                    {
                        // *******
                        // * STA *
                        // *******

                        // Store accumulator in memory
                        this.Write(addr, this.REG_ACC);
                        break;
                    }
                case 48:
                    {
                        // *******
                        // * STX *
                        // *******

                        // Store index X in memory
                        this.Write(addr, this.REG_X);
                        break;
                    }
                case 49:
                    {
                        // *******
                        // * STY *
                        // *******

                        // Store index Y in memory:
                        this.Write(addr, this.REG_Y);
                        break;
                    }
                case 50:
                    {
                        // *******
                        // * TAX *
                        // *******

                        // Transfer accumulator to index X:
                        this.REG_X = this.REG_ACC;
                        this.F_SIGN = (this.REG_ACC >> 7) & 1;
                        this.F_ZERO = this.REG_ACC;
                        break;
                    }
                case 51:
                    {
                        // *******
                        // * TAY *
                        // *******

                        // Transfer accumulator to index Y:
                        this.REG_Y = this.REG_ACC;
                        this.F_SIGN = (this.REG_ACC >> 7) & 1;
                        this.F_ZERO = this.REG_ACC;
                        break;
                    }
                case 52:
                    {
                        // *******
                        // * TSX *
                        // *******

                        // Transfer stack pointer to index X:
                        this.REG_X = this.REG_SP - 0x0100;
                        this.F_SIGN = (this.REG_SP >> 7) & 1;
                        this.F_ZERO = this.REG_X;
                        break;
                    }
                case 53:
                    {
                        // *******
                        // * TXA *
                        // *******

                        // Transfer index X to accumulator:
                        this.REG_ACC = this.REG_X;
                        this.F_SIGN = (this.REG_X >> 7) & 1;
                        this.F_ZERO = this.REG_X;
                        break;
                    }
                case 54:
                    {
                        // *******
                        // * TXS *
                        // *******

                        // Transfer index X to stack pointer:
                        this.REG_SP = this.REG_X + 0x0100;
                        this.StackWrap();
                        break;
                    }
                case 55:
                    {
                        // *******
                        // * TYA *
                        // *******

                        // Transfer index Y to accumulator:
                        this.REG_ACC = this.REG_Y;
                        this.F_SIGN = (this.REG_Y >> 7) & 1;
                        this.F_ZERO = this.REG_Y;
                        break;
                    }
                case 56:
                    {
                        // *******
                        // * ALR *
                        // *******

                        // Shift right one bit after ANDing:
                        temp = this.REG_ACC & this.Load(addr);
                        this.F_CARRY = temp & 1;
                        this.REG_ACC = this.F_ZERO = temp >> 1;
                        this.F_SIGN = 0;
                        break;
                    }
                case 57:
                    {
                        // *******
                        // * ANC *
                        // *******

                        // AND accumulator, setting carry to bit 7 result.
                        this.REG_ACC = this.F_ZERO = this.REG_ACC & this.Load(addr);
                        this.F_CARRY = this.F_SIGN = (this.REG_ACC >> 7) & 1;
                        break;
                    }
                case 58:
                    {
                        // *******
                        // * ARR *
                        // *******

                        // Rotate right one bit after ANDing:
                        temp = this.REG_ACC & this.Load(addr);
                        this.REG_ACC = this.F_ZERO = (temp >> 1) + (this.F_CARRY << 7);
                        this.F_SIGN = this.F_CARRY;
                        this.F_CARRY = (temp >> 7) & 1;
                        this.F_OVERFLOW = ((temp >> 7) ^ (temp >> 6)) & 1;
                        break;
                    }
                case 59:
                    {
                        // *******
                        // * AXS *
                        // *******

                        // Set X to (X AND A) - value.
                        temp = (this.REG_X & this.REG_ACC) - this.Load(addr);
                        this.F_SIGN = (temp >> 7) & 1;
                        this.F_ZERO = temp & 0xff;
                        if (
                          ((this.REG_X ^ temp) & 0x80) != 0 &&
                          ((this.REG_X ^ this.Load(addr)) & 0x80) != 0
                        )
                        {
                            this.F_OVERFLOW = 1;
                        }
                        else
                        {
                            this.F_OVERFLOW = 0;
                        }
                        this.F_CARRY = temp < 0 ? 0 : 1;
                        this.REG_X = temp & 0xff;
                        break;
                    }
                case 60:
                    {
                        // *******
                        // * LAX *
                        // *******

                        // Load A and X with memory:
                        this.REG_ACC = this.REG_X = this.F_ZERO = this.Load(addr);
                        this.F_SIGN = (this.REG_ACC >> 7) & 1;
                        cycleCount += cycleAdd;
                        break;
                    }
                case 61:
                    {
                        // *******
                        // * SAX *
                        // *******

                        // Store A AND X in memory:
                        this.Write(addr, this.REG_ACC & this.REG_X);
                        break;
                    }
                case 62:
                    {
                        // *******
                        // * DCP *
                        // *******

                        // Decrement memory by one:
                        temp = (this.Load(addr) - 1) & 0xff;
                        this.Write(addr, temp);

                        // Then compare with the accumulator:
                        temp = this.REG_ACC - temp;
                        this.F_CARRY = temp >= 0 ? 1 : 0;
                        this.F_SIGN = (temp >> 7) & 1;
                        this.F_ZERO = temp & 0xff;
                        if (addrMode != 11) cycleCount += cycleAdd; // PostIdxInd = 11
                        break;
                    }
                case 63:
                    {
                        // *******
                        // * ISC *
                        // *******

                        // Increment memory by one:
                        temp = (this.Load(addr) + 1) & 0xff;
                        this.Write(addr, temp);

                        // Then subtract from the accumulator:
                        temp = this.REG_ACC - temp - (1 - this.F_CARRY);
                        this.F_SIGN = (temp >> 7) & 1;
                        this.F_ZERO = temp & 0xff;
                        if (
                          ((this.REG_ACC ^ temp) & 0x80) != 0 &&
                          ((this.REG_ACC ^ this.Load(addr)) & 0x80) != 0
                        )
                        {
                            this.F_OVERFLOW = 1;
                        }
                        else
                        {
                            this.F_OVERFLOW = 0;
                        }
                        this.F_CARRY = temp < 0 ? 0 : 1;
                        this.REG_ACC = temp & 0xff;
                        if (addrMode != 11) cycleCount += cycleAdd; // PostIdxInd = 11
                        break;
                    }
                case 64:
                    {
                        // *******
                        // * RLA *
                        // *******

                        // Rotate one bit left
                        temp = this.Load(addr);
                        add = this.F_CARRY;
                        this.F_CARRY = (temp >> 7) & 1;
                        temp = ((temp << 1) & 0xff) + add;
                        this.Write(addr, temp);

                        // Then AND with the accumulator.
                        this.REG_ACC = this.REG_ACC & temp;
                        this.F_SIGN = (this.REG_ACC >> 7) & 1;
                        this.F_ZERO = this.REG_ACC;
                        if (addrMode != 11) cycleCount += cycleAdd; // PostIdxInd = 11
                        break;
                    }
                case 65:
                    {
                        // *******
                        // * RRA *
                        // *******

                        // Rotate one bit right
                        temp = this.Load(addr);
                        add = this.F_CARRY << 7;
                        this.F_CARRY = temp & 1;
                        temp = (temp >> 1) + add;
                        this.Write(addr, temp);

                        // Then add to the accumulator
                        temp = this.REG_ACC + this.Load(addr) + this.F_CARRY;

                        if (
                          ((this.REG_ACC ^ this.Load(addr)) & 0x80) == 0 &&
                          ((this.REG_ACC ^ temp) & 0x80) != 0
                        )
                        {
                            this.F_OVERFLOW = 1;
                        }
                        else
                        {
                            this.F_OVERFLOW = 0;
                        }
                        this.F_CARRY = temp > 255 ? 1 : 0;
                        this.F_SIGN = (temp >> 7) & 1;
                        this.F_ZERO = temp & 0xff;
                        this.REG_ACC = temp & 255;
                        if (addrMode != 11) cycleCount += cycleAdd; // PostIdxInd = 11
                        break;
                    }
                case 66:
                    {
                        // *******
                        // * SLO *
                        // *******

                        // Shift one bit left
                        temp = this.Load(addr);
                        this.F_CARRY = (temp >> 7) & 1;
                        temp = (temp << 1) & 255;
                        this.Write(addr, temp);

                        // Then OR with the accumulator.
                        this.REG_ACC = this.REG_ACC | temp;
                        this.F_SIGN = (this.REG_ACC >> 7) & 1;
                        this.F_ZERO = this.REG_ACC;
                        if (addrMode != 11) cycleCount += cycleAdd; // PostIdxInd = 11
                        break;
                    }
                case 67:
                    {
                        // *******
                        // * SRE *
                        // *******

                        // Shift one bit right
                        temp = this.Load(addr) & 0xff;
                        this.F_CARRY = temp & 1;
                        temp >>= 1;
                        this.Write(addr, temp);

                        // Then XOR with the accumulator.
                        this.REG_ACC = this.REG_ACC ^ temp;
                        this.F_SIGN = (this.REG_ACC >> 7) & 1;
                        this.F_ZERO = this.REG_ACC;
                        if (addrMode != 11) cycleCount += cycleAdd; // PostIdxInd = 11
                        break;
                    }
                case 68:
                    {
                        // *******
                        // * SKB *
                        // *******

                        // Do nothing
                        break;
                    }
                case 69:
                    {
                        // *******
                        // * IGN *
                        // *******

                        // Do nothing but load.
                        // TODO: Properly implement the double-reads.
                        this.Load(addr);
                        if (addrMode != 11) cycleCount += cycleAdd; // PostIdxInd = 11
                        break;
                    }

                default:
                    {
                        // *******
                        // * ??? *
                        // *******

                        //this.Nes.stop();
                        this.Nes.crashMessage =
                          "Game crashed, invalid opcode at address $" + opaddr.ToString("X");
                        break;
                    }
            } // end of switch

            return cycleCount;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Int32 Load (Int32 addr)
        {
            if (addr < 0x2000)
            {
                return this.Mem[addr & 0x7ff];
            }
            else
            {
                return this.Nes.Mmap.Load(addr);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Int32 Load16bit(Int32 addr)
        {
            if (addr < 0x1fff)
            {
                return this.Mem[addr & 0x7ff] | (this.Mem[(addr + 1) & 0x7ff] << 8);
            }
            else
            {
                return this.Nes.Mmap.Load(addr) | (this.Nes.Mmap.Load(addr + 1) << 8);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Write (Int32 addr, Int32 val)
        {
            if (addr < 0x2000)
            {
                this.Mem[addr & 0x7ff] = val;
            }
            else
            {
                this.Nes.Mmap.Write(addr, val);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void RequestIrq (Int32 type)
        {
            if (this.IrqRequested)
            {
                if (type == this.IRQ_NORMAL)
                {
                    return;
                }
                // console.log("too fast irqs. type="+type);
            }
            this.IrqRequested = true;
            this.IrqType = type;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Push(Int32 value)
        {
            this.Nes.Mmap.Write(this.REG_SP, value);
            this.REG_SP--;
            this.REG_SP = 0x0100 | (this.REG_SP & 0xff);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void StackWrap ()
        {
            this.REG_SP = 0x0100 | (this.REG_SP & 0xff);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Int32 Pull()
        {
            this.REG_SP++;
            this.REG_SP = 0x0100 | (this.REG_SP & 0xff);
            return this.Nes.Mmap.Load(this.REG_SP);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Boolean PageCrossed (Int32 addr1, Int32 addr2)
        {
            return (addr1 & 0xff00) != (addr2 & 0xff00);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void HaltCycles (Int32 cycles)
        {
            this.CyclesToHalt += cycles;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void DoNonMaskableInterrupt (Int32 status)
        {
            if ((this.Nes.Mmap.Load(0x2000) & 128) != 0)
            {
                // Check whether VBlank Interrupts are enabled

                this.REG_PC_NEW++;
                this.Push((this.REG_PC_NEW >> 8) & 0xff);
                this.Push(this.REG_PC_NEW & 0xff);
                //this.F_INTERRUPT_NEW = 1;
                this.Push(status);

                this.REG_PC_NEW =
                  this.Nes.Mmap.Load(0xfffa) | (this.Nes.Mmap.Load(0xfffb) << 8);
                this.REG_PC_NEW--;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void DoResetInterrupt ()
        {
            this.REG_PC_NEW =
            this.Nes.Mmap.Load(0xfffc) | (this.Nes.Mmap.Load(0xfffd) << 8);
            this.REG_PC_NEW--;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void DoIrq(Int32 status)
        {
            this.REG_PC_NEW++;
            this.Push((this.REG_PC_NEW >> 8) & 0xff);
            this.Push(this.REG_PC_NEW & 0xff);
            this.Push(status);
            this.F_INTERRUPT_NEW = 1;
            this.F_BRK_NEW = 0;

            this.REG_PC_NEW =
              this.Nes.Mmap.Load(0xfffe) | (this.Nes.Mmap.Load(0xffff) << 8);
            this.REG_PC_NEW--;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Int32 GetStatus ()
        {
            return (
              this.F_CARRY |
              (this.F_ZERO << 1) |
              (this.F_INTERRUPT << 2) |
              (this.F_DECIMAL << 3) |
              (this.F_BRK << 4) |
              (this.F_NOTUSED << 5) |
              (this.F_OVERFLOW << 6) |
              (this.F_SIGN << 7)
            );
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void SetStatus (Int32 st)
        {
            this.F_CARRY = st & 1;
            this.F_ZERO = (st >> 1) & 1;
            this.F_INTERRUPT = (st >> 2) & 1;
            this.F_DECIMAL = (st >> 3) & 1;
            this.F_BRK = (st >> 4) & 1;
            this.F_NOTUSED = (st >> 5) & 1;
            this.F_OVERFLOW = (st >> 6) & 1;
            this.F_SIGN = (st >> 7) & 1;
        }

        public String[] JSON_PROPERTIES = new String[]
        {
            "mem",
            "cyclesToHalt",
            "irqRequested",
            "irqType",
            // Registers
            "REG_ACC",
            "REG_X",
            "REG_Y",
            "REG_SP",
            "REG_PC",
            "REG_PC_NEW",
            "REG_STATUS",
            // Status
            "F_CARRY",
            "F_DECIMAL",
            "F_INTERRUPT",
            "F_INTERRUPT_NEW",
            "F_OVERFLOW",
            "F_SIGN",
            "F_ZERO",
            "F_NOTUSED",
            "F_NOTUSED_NEW",
            "F_BRK",
            "F_BRK_NEW",
        };

    }

    public class OpData
    {
        public Int32[] Opdata;
        public Int32[] CycTable;
        public String[] Instname;
        public String[] AddrDesc;


        public OpData()
        {
            this.Opdata = new Int32[256];

            // Set all to invalid instruction (to detect crashes):
            for (var i = 0; i < 256; i++) { this.Opdata[i] = 0xff; }

            #region Now fill in all valid opcodes:


            // ADC:
            this.SetOp(INS_ADC, 0x69, ADDR_IMM, 2, 2);
            this.SetOp(INS_ADC, 0x65, ADDR_ZP, 2, 3);
            this.SetOp(INS_ADC, 0x75, ADDR_ZPX, 2, 4);
            this.SetOp(INS_ADC, 0x6d, ADDR_ABS, 3, 4);
            this.SetOp(INS_ADC, 0x7d, ADDR_ABSX, 3, 4);
            this.SetOp(INS_ADC, 0x79, ADDR_ABSY, 3, 4);
            this.SetOp(INS_ADC, 0x61, ADDR_PREIDXIND, 2, 6);
            this.SetOp(INS_ADC, 0x71, ADDR_POSTIDXIND, 2, 5);

            // AND:
            this.SetOp(INS_AND, 0x29, ADDR_IMM, 2, 2);
            this.SetOp(INS_AND, 0x25, ADDR_ZP, 2, 3);
            this.SetOp(INS_AND, 0x35, ADDR_ZPX, 2, 4);
            this.SetOp(INS_AND, 0x2d, ADDR_ABS, 3, 4);
            this.SetOp(INS_AND, 0x3d, ADDR_ABSX, 3, 4);
            this.SetOp(INS_AND, 0x39, ADDR_ABSY, 3, 4);
            this.SetOp(INS_AND, 0x21, ADDR_PREIDXIND, 2, 6);
            this.SetOp(INS_AND, 0x31, ADDR_POSTIDXIND, 2, 5);

            // ASL:
            this.SetOp(INS_ASL, 0x0a, ADDR_ACC, 1, 2);
            this.SetOp(INS_ASL, 0x06, ADDR_ZP, 2, 5);
            this.SetOp(INS_ASL, 0x16, ADDR_ZPX, 2, 6);
            this.SetOp(INS_ASL, 0x0e, ADDR_ABS, 3, 6);
            this.SetOp(INS_ASL, 0x1e, ADDR_ABSX, 3, 7);

            // BCC:
            this.SetOp(INS_BCC, 0x90, ADDR_REL, 2, 2);

            // BCS:
            this.SetOp(INS_BCS, 0xb0, ADDR_REL, 2, 2);

            // BEQ:
            this.SetOp(INS_BEQ, 0xf0, ADDR_REL, 2, 2);

            // BIT:
            this.SetOp(INS_BIT, 0x24, ADDR_ZP, 2, 3);
            this.SetOp(INS_BIT, 0x2c, ADDR_ABS, 3, 4);

            // BMI:
            this.SetOp(INS_BMI, 0x30, ADDR_REL, 2, 2);

            // BNE:
            this.SetOp(INS_BNE, 0xd0, ADDR_REL, 2, 2);

            // BPL:
            this.SetOp(INS_BPL, 0x10, ADDR_REL, 2, 2);

            // BRK:
            this.SetOp(INS_BRK, 0x00, ADDR_IMP, 1, 7);

            // BVC:
            this.SetOp(INS_BVC, 0x50, ADDR_REL, 2, 2);

            // BVS:
            this.SetOp(INS_BVS, 0x70, ADDR_REL, 2, 2);

            // CLC:
            this.SetOp(INS_CLC, 0x18, ADDR_IMP, 1, 2);

            // CLD:
            this.SetOp(INS_CLD, 0xd8, ADDR_IMP, 1, 2);

            // CLI:
            this.SetOp(INS_CLI, 0x58, ADDR_IMP, 1, 2);

            // CLV:
            this.SetOp(INS_CLV, 0xb8, ADDR_IMP, 1, 2);

            // CMP:
            this.SetOp(INS_CMP, 0xc9, ADDR_IMM, 2, 2);
            this.SetOp(INS_CMP, 0xc5, ADDR_ZP, 2, 3);
            this.SetOp(INS_CMP, 0xd5, ADDR_ZPX, 2, 4);
            this.SetOp(INS_CMP, 0xcd, ADDR_ABS, 3, 4);
            this.SetOp(INS_CMP, 0xdd, ADDR_ABSX, 3, 4);
            this.SetOp(INS_CMP, 0xd9, ADDR_ABSY, 3, 4);
            this.SetOp(INS_CMP, 0xc1, ADDR_PREIDXIND, 2, 6);
            this.SetOp(INS_CMP, 0xd1, ADDR_POSTIDXIND, 2, 5);

            // CPX:
            this.SetOp(INS_CPX, 0xe0, ADDR_IMM, 2, 2);
            this.SetOp(INS_CPX, 0xe4, ADDR_ZP, 2, 3);
            this.SetOp(INS_CPX, 0xec, ADDR_ABS, 3, 4);

            // CPY:
            this.SetOp(INS_CPY, 0xc0, ADDR_IMM, 2, 2);
            this.SetOp(INS_CPY, 0xc4, ADDR_ZP, 2, 3);
            this.SetOp(INS_CPY, 0xcc, ADDR_ABS, 3, 4);

            // DEC:
            this.SetOp(INS_DEC, 0xc6, ADDR_ZP, 2, 5);
            this.SetOp(INS_DEC, 0xd6, ADDR_ZPX, 2, 6);
            this.SetOp(INS_DEC, 0xce, ADDR_ABS, 3, 6);
            this.SetOp(INS_DEC, 0xde, ADDR_ABSX, 3, 7);

            // DEX:
            this.SetOp(INS_DEX, 0xca, ADDR_IMP, 1, 2);

            // DEY:
            this.SetOp(INS_DEY, 0x88, ADDR_IMP, 1, 2);

            // EOR:
            this.SetOp(INS_EOR, 0x49, ADDR_IMM, 2, 2);
            this.SetOp(INS_EOR, 0x45, ADDR_ZP, 2, 3);
            this.SetOp(INS_EOR, 0x55, ADDR_ZPX, 2, 4);
            this.SetOp(INS_EOR, 0x4d, ADDR_ABS, 3, 4);
            this.SetOp(INS_EOR, 0x5d, ADDR_ABSX, 3, 4);
            this.SetOp(INS_EOR, 0x59, ADDR_ABSY, 3, 4);
            this.SetOp(INS_EOR, 0x41, ADDR_PREIDXIND, 2, 6);
            this.SetOp(INS_EOR, 0x51, ADDR_POSTIDXIND, 2, 5);

            // INC:
            this.SetOp(INS_INC, 0xe6, ADDR_ZP, 2, 5);
            this.SetOp(INS_INC, 0xf6, ADDR_ZPX, 2, 6);
            this.SetOp(INS_INC, 0xee, ADDR_ABS, 3, 6);
            this.SetOp(INS_INC, 0xfe, ADDR_ABSX, 3, 7);

            // INX:
            this.SetOp(INS_INX, 0xe8, ADDR_IMP, 1, 2);

            // INY:
            this.SetOp(INS_INY, 0xc8, ADDR_IMP, 1, 2);

            // JMP:
            this.SetOp(INS_JMP, 0x4c, ADDR_ABS, 3, 3);
            this.SetOp(INS_JMP, 0x6c, ADDR_INDABS, 3, 5);

            // JSR:
            this.SetOp(INS_JSR, 0x20, ADDR_ABS, 3, 6);

            // LDA:
            this.SetOp(INS_LDA, 0xa9, ADDR_IMM, 2, 2);
            this.SetOp(INS_LDA, 0xa5, ADDR_ZP, 2, 3);
            this.SetOp(INS_LDA, 0xb5, ADDR_ZPX, 2, 4);
            this.SetOp(INS_LDA, 0xad, ADDR_ABS, 3, 4);
            this.SetOp(INS_LDA, 0xbd, ADDR_ABSX, 3, 4);
            this.SetOp(INS_LDA, 0xb9, ADDR_ABSY, 3, 4);
            this.SetOp(INS_LDA, 0xa1, ADDR_PREIDXIND, 2, 6);
            this.SetOp(INS_LDA, 0xb1, ADDR_POSTIDXIND, 2, 5);

            // LDX:
            this.SetOp(INS_LDX, 0xa2, ADDR_IMM, 2, 2);
            this.SetOp(INS_LDX, 0xa6, ADDR_ZP, 2, 3);
            this.SetOp(INS_LDX, 0xb6, ADDR_ZPY, 2, 4);
            this.SetOp(INS_LDX, 0xae, ADDR_ABS, 3, 4);
            this.SetOp(INS_LDX, 0xbe, ADDR_ABSY, 3, 4);

            // LDY:
            this.SetOp(INS_LDY, 0xa0, ADDR_IMM, 2, 2);
            this.SetOp(INS_LDY, 0xa4, ADDR_ZP, 2, 3);
            this.SetOp(INS_LDY, 0xb4, ADDR_ZPX, 2, 4);
            this.SetOp(INS_LDY, 0xac, ADDR_ABS, 3, 4);
            this.SetOp(INS_LDY, 0xbc, ADDR_ABSX, 3, 4);

            // LSR:
            this.SetOp(INS_LSR, 0x4a, ADDR_ACC, 1, 2);
            this.SetOp(INS_LSR, 0x46, ADDR_ZP, 2, 5);
            this.SetOp(INS_LSR, 0x56, ADDR_ZPX, 2, 6);
            this.SetOp(INS_LSR, 0x4e, ADDR_ABS, 3, 6);
            this.SetOp(INS_LSR, 0x5e, ADDR_ABSX, 3, 7);

            // NOP:
            this.SetOp(INS_NOP, 0x1a, ADDR_IMP, 1, 2);
            this.SetOp(INS_NOP, 0x3a, ADDR_IMP, 1, 2);
            this.SetOp(INS_NOP, 0x5a, ADDR_IMP, 1, 2);
            this.SetOp(INS_NOP, 0x7a, ADDR_IMP, 1, 2);
            this.SetOp(INS_NOP, 0xda, ADDR_IMP, 1, 2);
            this.SetOp(INS_NOP, 0xea, ADDR_IMP, 1, 2);
            this.SetOp(INS_NOP, 0xfa, ADDR_IMP, 1, 2);

            // ORA:
            this.SetOp(INS_ORA, 0x09, ADDR_IMM, 2, 2);
            this.SetOp(INS_ORA, 0x05, ADDR_ZP, 2, 3);
            this.SetOp(INS_ORA, 0x15, ADDR_ZPX, 2, 4);
            this.SetOp(INS_ORA, 0x0d, ADDR_ABS, 3, 4);
            this.SetOp(INS_ORA, 0x1d, ADDR_ABSX, 3, 4);
            this.SetOp(INS_ORA, 0x19, ADDR_ABSY, 3, 4);
            this.SetOp(INS_ORA, 0x01, ADDR_PREIDXIND, 2, 6);
            this.SetOp(INS_ORA, 0x11, ADDR_POSTIDXIND, 2, 5);

            // PHA:
            this.SetOp(INS_PHA, 0x48, ADDR_IMP, 1, 3);

            // PHP:
            this.SetOp(INS_PHP, 0x08, ADDR_IMP, 1, 3);

            // PLA:
            this.SetOp(INS_PLA, 0x68, ADDR_IMP, 1, 4);

            // PLP:
            this.SetOp(INS_PLP, 0x28, ADDR_IMP, 1, 4);

            // ROL:
            this.SetOp(INS_ROL, 0x2a, ADDR_ACC, 1, 2);
            this.SetOp(INS_ROL, 0x26, ADDR_ZP, 2, 5);
            this.SetOp(INS_ROL, 0x36, ADDR_ZPX, 2, 6);
            this.SetOp(INS_ROL, 0x2e, ADDR_ABS, 3, 6);
            this.SetOp(INS_ROL, 0x3e, ADDR_ABSX, 3, 7);

            // ROR:
            this.SetOp(INS_ROR, 0x6a, ADDR_ACC, 1, 2);
            this.SetOp(INS_ROR, 0x66, ADDR_ZP, 2, 5);
            this.SetOp(INS_ROR, 0x76, ADDR_ZPX, 2, 6);
            this.SetOp(INS_ROR, 0x6e, ADDR_ABS, 3, 6);
            this.SetOp(INS_ROR, 0x7e, ADDR_ABSX, 3, 7);

            // RTI:
            this.SetOp(INS_RTI, 0x40, ADDR_IMP, 1, 6);

            // RTS:
            this.SetOp(INS_RTS, 0x60, ADDR_IMP, 1, 6);

            // SBC:
            this.SetOp(INS_SBC, 0xe9, ADDR_IMM, 2, 2);
            this.SetOp(INS_SBC, 0xe5, ADDR_ZP, 2, 3);
            this.SetOp(INS_SBC, 0xf5, ADDR_ZPX, 2, 4);
            this.SetOp(INS_SBC, 0xed, ADDR_ABS, 3, 4);
            this.SetOp(INS_SBC, 0xfd, ADDR_ABSX, 3, 4);
            this.SetOp(INS_SBC, 0xf9, ADDR_ABSY, 3, 4);
            this.SetOp(INS_SBC, 0xe1, ADDR_PREIDXIND, 2, 6);
            this.SetOp(INS_SBC, 0xf1, ADDR_POSTIDXIND, 2, 5);

            // SEC:
            this.SetOp(INS_SEC, 0x38, ADDR_IMP, 1, 2);

            // SED:
            this.SetOp(INS_SED, 0xf8, ADDR_IMP, 1, 2);

            // SEI:
            this.SetOp(INS_SEI, 0x78, ADDR_IMP, 1, 2);

            // STA:
            this.SetOp(INS_STA, 0x85, ADDR_ZP, 2, 3);
            this.SetOp(INS_STA, 0x95, ADDR_ZPX, 2, 4);
            this.SetOp(INS_STA, 0x8d, ADDR_ABS, 3, 4);
            this.SetOp(INS_STA, 0x9d, ADDR_ABSX, 3, 5);
            this.SetOp(INS_STA, 0x99, ADDR_ABSY, 3, 5);
            this.SetOp(INS_STA, 0x81, ADDR_PREIDXIND, 2, 6);
            this.SetOp(INS_STA, 0x91, ADDR_POSTIDXIND, 2, 6);

            // STX:
            this.SetOp(INS_STX, 0x86, ADDR_ZP, 2, 3);
            this.SetOp(INS_STX, 0x96, ADDR_ZPY, 2, 4);
            this.SetOp(INS_STX, 0x8e, ADDR_ABS, 3, 4);

            // STY:
            this.SetOp(INS_STY, 0x84, ADDR_ZP, 2, 3);
            this.SetOp(INS_STY, 0x94, ADDR_ZPX, 2, 4);
            this.SetOp(INS_STY, 0x8c, ADDR_ABS, 3, 4);

            // TAX:
            this.SetOp(INS_TAX, 0xaa, ADDR_IMP, 1, 2);

            // TAY:
            this.SetOp(INS_TAY, 0xa8, ADDR_IMP, 1, 2);

            // TSX:
            this.SetOp(INS_TSX, 0xba, ADDR_IMP, 1, 2);

            // TXA:
            this.SetOp(INS_TXA, 0x8a, ADDR_IMP, 1, 2);

            // TXS:
            this.SetOp(INS_TXS, 0x9a, ADDR_IMP, 1, 2);

            // TYA:
            this.SetOp(INS_TYA, 0x98, ADDR_IMP, 1, 2);

            // ALR:
            this.SetOp(INS_ALR, 0x4b, ADDR_IMM, 2, 2);

            // ANC:
            this.SetOp(INS_ANC, 0x0b, ADDR_IMM, 2, 2);
            this.SetOp(INS_ANC, 0x2b, ADDR_IMM, 2, 2);

            // ARR:
            this.SetOp(INS_ARR, 0x6b, ADDR_IMM, 2, 2);

            // AXS:
            this.SetOp(INS_AXS, 0xcb, ADDR_IMM, 2, 2);

            // LAX:
            this.SetOp(INS_LAX, 0xa3, ADDR_PREIDXIND, 2, 6);
            this.SetOp(INS_LAX, 0xa7, ADDR_ZP, 2, 3);
            this.SetOp(INS_LAX, 0xaf, ADDR_ABS, 3, 4);
            this.SetOp(INS_LAX, 0xb3, ADDR_POSTIDXIND, 2, 5);
            this.SetOp(INS_LAX, 0xb7, ADDR_ZPY, 2, 4);
            this.SetOp(INS_LAX, 0xbf, ADDR_ABSY, 3, 4);

            // SAX:
            this.SetOp(INS_SAX, 0x83, ADDR_PREIDXIND, 2, 6);
            this.SetOp(INS_SAX, 0x87, ADDR_ZP, 2, 3);
            this.SetOp(INS_SAX, 0x8f, ADDR_ABS, 3, 4);
            this.SetOp(INS_SAX, 0x97, ADDR_ZPY, 2, 4);

            // DCP:
            this.SetOp(INS_DCP, 0xc3, ADDR_PREIDXIND, 2, 8);
            this.SetOp(INS_DCP, 0xc7, ADDR_ZP, 2, 5);
            this.SetOp(INS_DCP, 0xcf, ADDR_ABS, 3, 6);
            this.SetOp(INS_DCP, 0xd3, ADDR_POSTIDXIND, 2, 8);
            this.SetOp(INS_DCP, 0xd7, ADDR_ZPX, 2, 6);
            this.SetOp(INS_DCP, 0xdb, ADDR_ABSY, 3, 7);
            this.SetOp(INS_DCP, 0xdf, ADDR_ABSX, 3, 7);

            // ISC:
            this.SetOp(INS_ISC, 0xe3, ADDR_PREIDXIND, 2, 8);
            this.SetOp(INS_ISC, 0xe7, ADDR_ZP, 2, 5);
            this.SetOp(INS_ISC, 0xef, ADDR_ABS, 3, 6);
            this.SetOp(INS_ISC, 0xf3, ADDR_POSTIDXIND, 2, 8);
            this.SetOp(INS_ISC, 0xf7, ADDR_ZPX, 2, 6);
            this.SetOp(INS_ISC, 0xfb, ADDR_ABSY, 3, 7);
            this.SetOp(INS_ISC, 0xff, ADDR_ABSX, 3, 7);

            // RLA:
            this.SetOp(INS_RLA, 0x23, ADDR_PREIDXIND, 2, 8);
            this.SetOp(INS_RLA, 0x27, ADDR_ZP, 2, 5);
            this.SetOp(INS_RLA, 0x2f, ADDR_ABS, 3, 6);
            this.SetOp(INS_RLA, 0x33, ADDR_POSTIDXIND, 2, 8);
            this.SetOp(INS_RLA, 0x37, ADDR_ZPX, 2, 6);
            this.SetOp(INS_RLA, 0x3b, ADDR_ABSY, 3, 7);
            this.SetOp(INS_RLA, 0x3f, ADDR_ABSX, 3, 7);

            // RRA:
            this.SetOp(INS_RRA, 0x63, ADDR_PREIDXIND, 2, 8);
            this.SetOp(INS_RRA, 0x67, ADDR_ZP, 2, 5);
            this.SetOp(INS_RRA, 0x6f, ADDR_ABS, 3, 6);
            this.SetOp(INS_RRA, 0x73, ADDR_POSTIDXIND, 2, 8);
            this.SetOp(INS_RRA, 0x77, ADDR_ZPX, 2, 6);
            this.SetOp(INS_RRA, 0x7b, ADDR_ABSY, 3, 7);
            this.SetOp(INS_RRA, 0x7f, ADDR_ABSX, 3, 7);

            // SLO:
            this.SetOp(INS_SLO, 0x03, ADDR_PREIDXIND, 2, 8);
            this.SetOp(INS_SLO, 0x07, ADDR_ZP, 2, 5);
            this.SetOp(INS_SLO, 0x0f, ADDR_ABS, 3, 6);
            this.SetOp(INS_SLO, 0x13, ADDR_POSTIDXIND, 2, 8);
            this.SetOp(INS_SLO, 0x17, ADDR_ZPX, 2, 6);
            this.SetOp(INS_SLO, 0x1b, ADDR_ABSY, 3, 7);
            this.SetOp(INS_SLO, 0x1f, ADDR_ABSX, 3, 7);

            // SRE:
            this.SetOp(INS_SRE, 0x43, ADDR_PREIDXIND, 2, 8);
            this.SetOp(INS_SRE, 0x47, ADDR_ZP, 2, 5);
            this.SetOp(INS_SRE, 0x4f, ADDR_ABS, 3, 6);
            this.SetOp(INS_SRE, 0x53, ADDR_POSTIDXIND, 2, 8);
            this.SetOp(INS_SRE, 0x57, ADDR_ZPX, 2, 6);
            this.SetOp(INS_SRE, 0x5b, ADDR_ABSY, 3, 7);
            this.SetOp(INS_SRE, 0x5f, ADDR_ABSX, 3, 7);

            // SKB:
            this.SetOp(INS_SKB, 0x80, ADDR_IMM, 2, 2);
            this.SetOp(INS_SKB, 0x82, ADDR_IMM, 2, 2);
            this.SetOp(INS_SKB, 0x89, ADDR_IMM, 2, 2);
            this.SetOp(INS_SKB, 0xc2, ADDR_IMM, 2, 2);
            this.SetOp(INS_SKB, 0xe2, ADDR_IMM, 2, 2);

            // SKB:
            this.SetOp(INS_IGN, 0x0c, ADDR_ABS, 3, 4);
            this.SetOp(INS_IGN, 0x1c, ADDR_ABSX, 3, 4);
            this.SetOp(INS_IGN, 0x3c, ADDR_ABSX, 3, 4);
            this.SetOp(INS_IGN, 0x5c, ADDR_ABSX, 3, 4);
            this.SetOp(INS_IGN, 0x7c, ADDR_ABSX, 3, 4);
            this.SetOp(INS_IGN, 0xdc, ADDR_ABSX, 3, 4);
            this.SetOp(INS_IGN, 0xfc, ADDR_ABSX, 3, 4);
            this.SetOp(INS_IGN, 0x04, ADDR_ZP, 2, 3);
            this.SetOp(INS_IGN, 0x44, ADDR_ZP, 2, 3);
            this.SetOp(INS_IGN, 0x64, ADDR_ZP, 2, 3);
            this.SetOp(INS_IGN, 0x14, ADDR_ZPX, 2, 4);
            this.SetOp(INS_IGN, 0x34, ADDR_ZPX, 2, 4);
            this.SetOp(INS_IGN, 0x54, ADDR_ZPX, 2, 4);
            this.SetOp(INS_IGN, 0x74, ADDR_ZPX, 2, 4);
            this.SetOp(INS_IGN, 0xd4, ADDR_ZPX, 2, 4);
            this.SetOp(INS_IGN, 0xf4, ADDR_ZPX, 2, 4);
            #endregion

            // prettier-ignore
            this.CycTable = new Int32[]{
              /*0x00*/ 7, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6,
              /*0x10*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
              /*0x20*/ 6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 4, 4, 6, 6,
              /*0x30*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
              /*0x40*/ 6, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 3, 4, 6, 6,
              /*0x50*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
              /*0x60*/ 6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 5, 4, 6, 6,
              /*0x70*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
              /*0x80*/ 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4,
              /*0x90*/ 2, 6, 2, 6, 4, 4, 4, 4, 2, 5, 2, 5, 5, 5, 5, 5,
              /*0xA0*/ 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4,
              /*0xB0*/ 2, 5, 2, 5, 4, 4, 4, 4, 2, 4, 2, 4, 4, 4, 4, 4,
              /*0xC0*/ 2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6,
              /*0xD0*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7,
              /*0xE0*/ 2, 6, 3, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6,
              /*0xF0*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7
            };

            this.Instname = new String[70];

            // Instruction Names:
            this.Instname[0] = "ADC";
            this.Instname[1] = "AND";
            this.Instname[2] = "ASL";
            this.Instname[3] = "BCC";
            this.Instname[4] = "BCS";
            this.Instname[5] = "BEQ";
            this.Instname[6] = "BIT";
            this.Instname[7] = "BMI";
            this.Instname[8] = "BNE";
            this.Instname[9] = "BPL";
            this.Instname[10] = "BRK";
            this.Instname[11] = "BVC";
            this.Instname[12] = "BVS";
            this.Instname[13] = "CLC";
            this.Instname[14] = "CLD";
            this.Instname[15] = "CLI";
            this.Instname[16] = "CLV";
            this.Instname[17] = "CMP";
            this.Instname[18] = "CPX";
            this.Instname[19] = "CPY";
            this.Instname[20] = "DEC";
            this.Instname[21] = "DEX";
            this.Instname[22] = "DEY";
            this.Instname[23] = "EOR";
            this.Instname[24] = "INC";
            this.Instname[25] = "INX";
            this.Instname[26] = "INY";
            this.Instname[27] = "JMP";
            this.Instname[28] = "JSR";
            this.Instname[29] = "LDA";
            this.Instname[30] = "LDX";
            this.Instname[31] = "LDY";
            this.Instname[32] = "LSR";
            this.Instname[33] = "NOP";
            this.Instname[34] = "ORA";
            this.Instname[35] = "PHA";
            this.Instname[36] = "PHP";
            this.Instname[37] = "PLA";
            this.Instname[38] = "PLP";
            this.Instname[39] = "ROL";
            this.Instname[40] = "ROR";
            this.Instname[41] = "RTI";
            this.Instname[42] = "RTS";
            this.Instname[43] = "SBC";
            this.Instname[44] = "SEC";
            this.Instname[45] = "SED";
            this.Instname[46] = "SEI";
            this.Instname[47] = "STA";
            this.Instname[48] = "STX";
            this.Instname[49] = "STY";
            this.Instname[50] = "TAX";
            this.Instname[51] = "TAY";
            this.Instname[52] = "TSX";
            this.Instname[53] = "TXA";
            this.Instname[54] = "TXS";
            this.Instname[55] = "TYA";
            this.Instname[56] = "ALR";
            this.Instname[57] = "ANC";
            this.Instname[58] = "ARR";
            this.Instname[59] = "AXS";
            this.Instname[60] = "LAX";
            this.Instname[61] = "SAX";
            this.Instname[62] = "DCP";
            this.Instname[63] = "ISC";
            this.Instname[64] = "RLA";
            this.Instname[65] = "RRA";
            this.Instname[66] = "SLO";
            this.Instname[67] = "SRE";
            this.Instname[68] = "SKB";
            this.Instname[69] = "IGN";

            this.AddrDesc = new String[]{
              "Zero Page           ",
              "Relative            ",
              "Implied             ",
              "Absolute            ",
              "Accumulator         ",
              "Immediate           ",
              "Zero Page,X         ",
              "Zero Page,Y         ",
              "Absolute,X          ",
              "Absolute,Y          ",
              "Preindexed Indirect ",
              "Postindexed Indirect",
              "Indirect Absolute   "
            };
        }

        public const Int32 INS_ADC = 0;
        public const Int32 INS_AND = 1;
        public const Int32 INS_ASL = 2;

        public const Int32 INS_BCC = 3;
        public const Int32 INS_BCS = 4;
        public const Int32 INS_BEQ = 5;
        public const Int32 INS_BIT = 6;
        public const Int32 INS_BMI = 7;
        public const Int32 INS_BNE = 8;
        public const Int32 INS_BPL = 9;
        public const Int32 INS_BRK = 10;
        public const Int32 INS_BVC = 11;
        public const Int32 INS_BVS = 12;
        public const Int32 INS_CLC = 13;
        public const Int32 INS_CLD = 14;
        public const Int32 INS_CLI = 15;
        public const Int32 INS_CLV = 16;
        public const Int32 INS_CMP = 17;
        public const Int32 INS_CPX = 18;
        public const Int32 INS_CPY = 19;
        public const Int32 INS_DEC = 20;
        public const Int32 INS_DEX = 21;
        public const Int32 INS_DEY = 22;
        public const Int32 INS_EOR = 23;
        public const Int32 INS_INC = 24;
        public const Int32 INS_INX = 25;
        public const Int32 INS_INY = 26;
        public const Int32 INS_JMP = 27;
        public const Int32 INS_JSR = 28;
        public const Int32 INS_LDA = 29;
        public const Int32 INS_LDX = 30;
        public const Int32 INS_LDY = 31;
        public const Int32 INS_LSR = 32;
        public const Int32 INS_NOP = 33;
        public const Int32 INS_ORA = 34;
        public const Int32 INS_PHA = 35;
        public const Int32 INS_PHP = 36;
        public const Int32 INS_PLA = 37;
        public const Int32 INS_PLP = 38;
        public const Int32 INS_ROL = 39;
        public const Int32 INS_ROR = 40;
        public const Int32 INS_RTI = 41;
        public const Int32 INS_RTS = 42;
        public const Int32 INS_SBC = 43;
        public const Int32 INS_SEC = 44;
        public const Int32 INS_SED = 45;
        public const Int32 INS_SEI = 46;
        public const Int32 INS_STA = 47;
        public const Int32 INS_STX = 48;
        public const Int32 INS_STY = 49;
        public const Int32 INS_TAX = 50;
        public const Int32 INS_TAY = 51;
        public const Int32 INS_TSX = 52;
        public const Int32 INS_TXA = 53;
        public const Int32 INS_TXS = 54;
        public const Int32 INS_TYA = 55;
        public const Int32 INS_ALR = 56;
        public const Int32 INS_ANC = 57;
        public const Int32 INS_ARR = 58;
        public const Int32 INS_AXS = 59;
        public const Int32 INS_LAX = 60;
        public const Int32 INS_SAX = 61;
        public const Int32 INS_DCP = 62;
        public const Int32 INS_ISC = 63;
        public const Int32 INS_RLA = 64;
        public const Int32 INS_RRA = 65;
        public const Int32 INS_SLO = 66;
        public const Int32 INS_SRE = 67;
        public const Int32 INS_SKB = 68;
        public const Int32 INS_IGN = 69;

        public const Int32 INS_DUMMY = 70; // dummy instruction used for 'halting' the processor some cycles

        // -------------------------------- //

        // Addressing modes:
        public const Int32 ADDR_ZP = 0;         // 指定 Zero Page 地址
        public const Int32 ADDR_REL = 1;        // 相对寻址
        public const Int32 ADDR_IMP = 2;        // 特殊指令的寻址方式
        public const Int32 ADDR_ABS = 3;        // 绝对寻址
        public const Int32 ADDR_ACC = 4;        // 累加器 A 寻址
        public const Int32 ADDR_IMM = 5;        // 指定一个字节的数据
        public const Int32 ADDR_ZPX = 6;        // 指定 Zero Page 地址加上 X
        public const Int32 ADDR_ZPY = 7;        // 指定 Zero Page 地址加上 Y
        public const Int32 ADDR_ABSX = 8;       // 绝对寻址加上 X
        public const Int32 ADDR_ABSY = 9;       // 绝对寻址加上 Y
        public const Int32 ADDR_PREIDXIND = 10; //
        public const Int32 ADDR_POSTIDXIND = 11;//
        public const Int32 ADDR_INDABS = 12;    // 间接寻址（只有 JMP 使用）

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void SetOp(Int32 inst, Int32 op, Int32 addr, Int32 size, Int32 cycles)
        {
            this.Opdata[op] =
              (inst & 0xff) |
              ((addr & 0xff) << 8) |
              ((size & 0xff) << 16) |
              ((cycles & 0xff) << 24);
        }
    }
}
